#include "Forest.h"
#include "Weather.h"
#include "../Managers/GameManager.h"
#include "../GameObjects/Player.h"
#include "../Math/Vec3f.h"
#include <sstream>

Forest::Forest(const Vec2i &size)
	: map_size(size)
{
}

Forest::Forest(Vec2i &&size)
	: map_size(size)
{
}

Forest::Forest(int width, int height)
	: map_size(width, height)
{
}

Forest::~Forest()
{
}

void Forest::init()
{
	height_map = std::make_shared<TCODHeightMap>(map_size.x()*2, map_size.y()*2);
	TCODRandom ran(103);
	noise = std::make_shared<TCODNoise>(2, &ran);
	//height_map->addFbm(noise.get(),2.20*map_size.x()/200.0f,2.20*map_size.x()/200.0f,0,0,10.0f,1.0,100.0f);
	//height_map->addHill(map_size.x()/2.0f, map_size.y()/2.0f, 20.0f, 200.0f);
	TCODHeightMap hm1(map_size.x()*2, map_size.y()*2);
	TCODHeightMap hm2(map_size.x()*2, map_size.y()*2);
	hm1.addFbm(noise.get(), 0.5f, 0.75f, 0.001f,0.0001f, 4, 0.0005, 200.0f);
	hm2.addFbm(noise.get(), 4.06f, 4.04f, 0,0, 12, 0.0001f, 20.0f);
	height_map->add(&hm2, &hm1);
	height_map->addFbm(noise.get(), 0.75f, 0.5f, 0.0f,0.0, 2, 0.00001f, 40.0f);
	
	//height_map->normalize();

	fov_map = std::make_shared<TCODMap>(map_size.x(), map_size.y());
	fov_map->clear(true, true);

	for(unsigned int y = 0; y < map_size.y(); y++)
	{
		for(unsigned int x = 0; x < map_size.x(); x++)
		{
			float norm_f[3];
			height_map->getNormal(x,y,norm_f);
			Vec3f norm(norm_f[0], norm_f[2], norm_f[1]);
			norm.normalize();
			auto cell = std::make_shared<MapCell>(x, y, height_map->getValue(x,y), height_map->getSlope(x,y), norm);
			cells.push_back(cell);

			if(cell->terrainType->id <= TERRAIN_DIRT)
			{
				auto chance = ran.get(0.0, 1.0);
				if(chance < 0.4)
				{
					auto radius = ran.getInt(1,5);
					bool plant_tree = true;
					for(unsigned int i = 0; i < radius; i++)
					{
						int dx = x-i;
						if(dx >= 0)
						{
							auto index = (dx)+(y)*map_size.x();
							if(cells[index]->canPlantTree() == false)
								plant_tree = false;
							}

						int dy = y-i;
						if(dy >= 0)
						{			
							auto index = (x)+(dy)*map_size.x();
							if(cells[index]->canPlantTree() == false)
								plant_tree = false;
						}

						if(dx >= 0 && dy >= 0)
						{
							auto index = (dx)+(dy)*map_size.x();
							if(cells[index]->canPlantTree() == false)
								plant_tree = false;
						}
					}

					if(plant_tree)
					{
						auto tree = std::make_shared<Tree>(radius);
						trees.push_back(tree);
						fov_map->setProperties(x,y, false, false);
						cell->setWalkable(false);
						cell->setTreePlanted(tree);
						for(unsigned int i = 0; i < radius; i++)
						{
							int dx = x-i;
							if(dx >= 0)
							{
								auto index = (dx)+(y)*map_size.x();
								if(cells[index]->canPlantTree())
									cells[index]->setTreePlanted();
							}
							int dy = y-i;
							if(dy >= 0)
							{
								auto index = (x)+(dy)*map_size.x();
								if(cells[index]->canPlantTree())
									cells[index]->setTreePlanted();
							}
							if(dx >= 0 && dy >= 0)
							{
								auto index = (dx)+(dy)*map_size.x();
								if(cells[index]->canPlantTree())
									cells[index]->setTreePlanted();
							}
						}
					}
				}
			}
		}
	}
	
}

void Forest::update()
{
}
//map=(7,3), pp=(3,1),
//vp=(5,3), vp/2=(3,2)
//mv_s=(1,0) = pp - ((vp/2)-1) = (3-(3-1), 1-(2-1)) = (1,0)
//mv_e=(5,2) = pp + ((vp/2)-1) = (3+(3-1), 1+(2-1)) = (5,2)
/**********************
/*00*01*02*03*04*05*06*
/**********************	
/*07*08*09*10*11*12*13*
/**********************
/*14*15*16*17*18*19*20*
/**********************/
void Forest::render()
{
	auto console = GameManager::getSingleton()->getMapConsole();
	auto viewport = GameManager::getSingleton()->getMapViewport();
	auto &player_pos = GameManager::getSingleton()->getPlayer()->getPos();
	auto player_bounds = (viewport/2);

	auto map_view_start = player_pos - (player_bounds);
	auto map_view_end = player_pos + (player_bounds);

	float player_altitude = 0.0f;
	int lookup1d_p = player_pos.x()+player_pos.y()*map_size.x();
	if(lookup1d_p < cells.size())
	{
		auto player_cell = cells[lookup1d_p];
		player_altitude = player_cell->getAltitude();
	}

	fov_map->computeFov(player_pos.x(), player_pos.y(), 40, true, FOV_DIAMOND);

	auto sun = GameManager::getSingleton()->getWeather()->getSun();
	Vec3f sun_pos = sun->getPos3f();

	int x_count = 0;
	int y_count = 0;
	for(int x = map_view_start.x(); x < map_view_end.x()-1; x++)
	{
		for(int y = map_view_start.y(); y < map_view_end.y()-1; y++)
		{
			int lookup1d = x+y*map_size.x();
			if(lookup1d >= cells.size())
			{
				y_count++;
				continue;
			}
			
			auto cell = cells[lookup1d];

			if(fov_map->isInFov(cell->getPos().x(), cell->getPos().y()) == false)
			{
				console->setCharBackground(x_count, y_count, TCODColor::black);
				console->putChar(x_count, y_count, 219, TCOD_BKGND_NONE);
				console->setCharForeground(x_count,y_count, TCODColor::black);
				y_count++;
				continue;
			}

			if(player_altitude + 10.0f < cell->getAltitude())
			{
				console->setCharBackground(x_count, y_count, TCODColor::black);
				console->putChar(x_count, y_count, 24, TCOD_BKGND_NONE);
				console->setCharForeground(x_count,y_count, TCODColor::darkerGrey);
				y_count++;
				continue;
			}
			if(player_altitude - 10.0f > cell->getAltitude())
			{
				console->setCharBackground(x_count, y_count, TCODColor::black);
				console->putChar(x_count, y_count, 25, TCOD_BKGND_NONE);
				console->setCharForeground(x_count,y_count, TCODColor::darkerGrey);
				y_count++;
				continue;
			}

			float cosAngIncidence = 0.0f;
			float att = 1.0f;
			float coef = sun->getCoef();
			auto ambient = sun->getAmbient();
			auto sun_color = sun->getColor();

			Vec3f cell_pos = Vec3f(x,cell->getAltitude(),y);
			Vec3f L = sun_pos - cell_pos;
			float sun_distance = L.length();
			L.normalize();
			Vec3f N = cell->getNormal();
			if(N == Vec3f(0.0f, 1.0f, 0.0f))
			{
				N.x() += 0.001f;
				N.y() += 0.001f;
			}

			Vec3f sun_color_f(sun_color.r / 255.0f, sun_color.g / 255.0f, sun_color.b / 255.0f);

			float lightConstAtt = 0.0f;
			float lightLinearAtt = 0.0f;
			float lightQuadAtt = 0.0f; //0.000095f;
			if(lightConstAtt > 0.0f || lightLinearAtt > 0.0f || lightQuadAtt > 0.0f)
				att = 1.0f / ( lightConstAtt + (lightLinearAtt*sun_distance) + (lightQuadAtt*sun_distance*sun_distance) );
			Vec3f attIntensity = sun_color_f * att;

			///cosAngIncidence = N.dot(L);
			cosAngIncidence = Vec3f::clampf(N.dot(L), 0.0f, 1.0f);

			Vec3f viewDirection(0.0f, -1.0f, 0.0f);
			viewDirection.normalize();

			////Phong term
			/*Vec3f reflectDir = N.reflect(L.inverted());
			float term = Vec3f::clampf(viewDirection.dot(reflectDir), 0.0f, 1.0f);
			term = cosAngIncidence != 0.0f ? term : 0.0f;
			term = powf(term, cell->terrainType->shininess);*/

			//Blinn term
			Vec3f halfAngle = (L + viewDirection).normalize();
			float term = N.dot(halfAngle);
			term = Vec3f::clampf(term, 0.0f, 1.0f);
			term = cosAngIncidence != 0.0f ? term : 0.0f;
			term = powf(term, cell->terrainType->shininess);

			//Gaussian term
			/*Vec3f halfAngle = (L + viewDirection).normalize();
			float angleNormalHalf = acosf(N.dot(halfAngle));
			float exponent = angleNormalHalf / cell->terrainType->shininess;
			exponent = -(exponent * exponent);
			float term = exp(exponent);
			term = cosAngIncidence != 0.0 ? term : 0.0;*/

			TCODColor bkgr_color_out;
			{
				Vec3f diffuseColor(cell->terrainType->bkgr_color.r / 255.0f, cell->terrainType->bkgr_color.g / 255.0f, cell->terrainType->bkgr_color.b / 255.0f);
				Vec3f specularColor(diffuseColor*cell->terrainType->shininess);
				//Vec3f ambientColor(ambient.r / 255.0f, ambient.g / 255.0f, ambient.b / 255.0f);
				Vec3f ambientColor(0.2f, 0.2f, 0.2f);
				Vec3f color_out_f = (diffuseColor * attIntensity * cosAngIncidence) + (specularColor * attIntensity * term) + (diffuseColor * ambientColor);
				Vec3f clamped_color_out_f = Vec3f::clamp(color_out_f, 0.0f, 1.0f);

				bkgr_color_out = TCODColor((uint8)(clamped_color_out_f.x()*255.0f), (uint8)(clamped_color_out_f.y()*255.0f), (uint8)(clamped_color_out_f.z()*255.0f));
				console->setCharBackground(x_count, y_count, bkgr_color_out);
			}
			TCODColor frgr_color_out;
			{
				Vec3f diffuseColor(cell->terrainType->frgr_color.r / 255.0f, cell->terrainType->frgr_color.g / 255.0f, cell->terrainType->frgr_color.b / 255.0f);
				Vec3f specularColor(diffuseColor*cell->terrainType->shininess);
				//Vec3f ambientColor(ambient.r / 255.0f, ambient.g / 255.0f, ambient.b / 255.0f);
				Vec3f ambientColor(0.2f, 0.2f, 0.2f);
				Vec3f color_out_f = (diffuseColor * attIntensity * cosAngIncidence) + (specularColor * attIntensity * term) + (diffuseColor * ambientColor);
				Vec3f clamped_color_out_f = Vec3f::clamp(color_out_f, 0.0f, 1.0f);

				frgr_color_out = TCODColor((uint8)(clamped_color_out_f.x()*255.0f), (uint8)(clamped_color_out_f.y()*255.0f), (uint8)(clamped_color_out_f.z()*255.0f));
				console->putChar(x_count, y_count, cell->terrainType->frgr_symbol, TCOD_BKGND_NONE);
				console->setCharForeground(x_count,y_count, frgr_color_out);
			}

			

			if(sun->getPos().x() == x && sun->getPos().y() == y)
			{
				console->setCharBackground(x_count, y_count, TCODColor::yellow);
				console->setCharForeground(x_count,y_count, TCODColor::yellow);
			}

			if(x == player_pos.x() && y == player_pos.y())
			{
				TCODColor player_color_out;
				{
					auto &player_color = GameManager::getSingleton()->getPlayer()->getColor();
					Vec3f diffuseColor(player_color.r / 255.0f, player_color.g / 255.0f, player_color.b / 255.0f);
					Vec3f specularColor(diffuseColor*cell->terrainType->shininess);
					//Vec3f ambientColor(ambient.r / 255.0f, ambient.g / 255.0f, ambient.b / 255.0f);
					Vec3f ambientColor(0.2f, 0.2f, 0.2f);
					Vec3f color_out_f = (diffuseColor * attIntensity * cosAngIncidence) + (specularColor * attIntensity * term) + (diffuseColor * ambientColor);
					Vec3f clamped_color_out_f = Vec3f::clamp(color_out_f, 0.0f, 1.0f);

					player_color_out = TCODColor((uint8)(clamped_color_out_f.x()*255.0f), (uint8)(clamped_color_out_f.y()*255.0f), (uint8)(clamped_color_out_f.z()*255.0f));
					console->putChar(x_count, y_count, GameManager::getSingleton()->getPlayer()->getTile(), TCOD_BKGND_NONE);
					console->setCharForeground(x_count,y_count, player_color_out);
				}
				std::stringstream ss;
				ss << "P: (" << x << ", " << cell->getAltitude() << ", " << y << ") S: " << cell->getSlope() << " N: (" << cell->getNormal().x() << ", " << cell->getNormal().y() << ", " << cell->getNormal().z() << ") BC: " << (int)bkgr_color_out.r << ", " << (int)bkgr_color_out.g << ", " << (int)bkgr_color_out.b << ").";
				GameManager::getSingleton()->getStoryConsole()->print(0,2,ss.str().c_str());
				std::stringstream ss2;
				ss2 << "SUN: (" << sun_pos.x() << ", " << sun_pos.y() << ", " << sun_pos.z() << ") T: " << term << " FC: " << (int)frgr_color_out.r << ", " << (int)frgr_color_out.g << ", " << (int)frgr_color_out.b << ").";
				GameManager::getSingleton()->getStoryConsole()->print(0,3,ss2.str().c_str());
			}
			else
			{
				auto tree = cell->getTree();
				if(tree)
				{
					auto &tree_color = tree->getColor();
					Vec3f diffuseColor(tree_color.r / 255.0f, tree_color.g / 255.0f, tree_color.b / 255.0f);
					Vec3f specularColor(diffuseColor*0.4f);
					//Vec3f ambientColor(ambient.r / 255.0f, ambient.g / 255.0f, ambient.b / 255.0f);
					Vec3f ambientColor(0.2f, 0.2f, 0.2f);
					Vec3f color_out_f = (diffuseColor * attIntensity * cosAngIncidence) + (specularColor * attIntensity * term) + (diffuseColor * ambientColor);
					Vec3f clamped_color_out_f = Vec3f::clamp(color_out_f, 0.0f, 1.0f);

					TCODColor tree_color_out = TCODColor((uint8)(clamped_color_out_f.x()*255.0f), (uint8)(clamped_color_out_f.y()*255.0f), (uint8)(clamped_color_out_f.z()*255.0f));
					console->putChar(x_count, y_count, tree->getTile(), TCOD_BKGND_NONE);
					console->setCharForeground(x_count,y_count, tree_color_out);
				}
			}
			
			y_count++;
		}
		y_count=0;
		x_count++;
	}
}

const MapCellPtr &Forest::getCellAt(const Vec2i &pos)
{
	auto index = pos.x()+pos.y()*map_size.x();
	if(index < cells.size())
		return cells[index];
	else
		return null_cell;
}
